• Глава 10. Шаблоны

    Глава 10. Шаблоны

    Подобно тому, как класс является схемой для создания своих представителей-объектов, шаблон класса в C++ является схемой для образования конкретных представителей-классов шаблона, или шаблонных классов. Шаблоны классов называют иногда параметризованными типами, поскольку действительный тип (класс) создается посредством спецификации конкретных параметров шаблона.

    Можно определять также шаблоны функций, с которых мы и начнем.

    Шаблоны функций

    Синтаксис определения шаблона функции имеет вид:

    template <список формальных типов>возвращаемый_тип имя_функции(список параметров) {

    тело функции }

    Список_формальных_типов состоит из спецификаций вида class формалъный_тип, разделенных запятыми. Формальный тип может обозначаться любым идентификатором, аналогично формальному параметру функции.

    Список _параметров функции должен включать в себя параметры типов, перечисленных в списке формальных _типов, и еще, возможно, какие-то другие. Возвращаемый_тип также может быть одним из формальных типов. Например:

    template <class T> void Func1 (Та, Т b) { ... }

    template <class T> T Func2(Т a, int b) { ... }

    template <class Tl, class T2> long Func3(Tl a, T2 b) { ... }

    Как видите, определение шаблона функции отличается от обычной функции только наличием конструкции template <список_формальных_типов> в заголовке.

    В качестве примера шаблонов функций можно привести определение функций min () и max () из заголовочного файла sdlib.h. Определение это сводится к следующему:

    template <class T> inline const Т &min(const Т&t1, const T&t2)

    if (t1 < t2)

    return tl;

    else

    return t2;

    }

    template <class T>

    inline const T &max(const T &tl, const T &t2)

    {

    if (t1 > t2) return t1;

    else

    return t2;

    }

    Эти функции можно вызывать с аргументами любого типа (класса), в котором определены операции “больше-меньше”.

    Когда компилятор встречает вызов функции шаблона, он автоматически порождает представитель шаблона, подставляя вместо формального типа конкретный тип аргумента, с которым вызывается функция.

    Шаблоны функций размещают чаще всего в заголовочных файлах, подобно определениям макросов и inline-функций.

    Перегрузка шаблонов функций

    Шаблоны функций можно перегружать точно так же, как обычные функции. Два шаблона могут иметь одно и то же имя, если их можно различить по списку параметров, например:

    // Возвращает больший из двух аргументов.

    template <class Т> Т Мах(Т а, Тb) {

    return а > b? а : b;

    // Возвращает наибольший элемент массива. template <class Т> Т Мах(Т аrr[], size_t size)

    (

    Т maxVal = arr[0] ;

    for(int i=l; i<size; i++) if (arr[i] > maxVal) maxVal = arr[i];

    return maxVal;

    }

    Специализация шаблона функции

    Несколько напоминает перегрузку шаблонов ситуация, когда определяется обычная функция, имя которой совпадает с именем шаблона и список параметров которой соответствует шаблону с некоторым специфическим набором фактических типов. Такую функцию называют специализированной функцией шаблона. Этот прием применяют, когда для некоторого типа или набора типов общий шаблон работать не будет.

    Допустим, мы хотим, чтобы шаблон Мах()из последнего примера порождал функцию для двух аргументов-строк, которая возвращала бы большую из них (в смысле алфавитного порядка). Функция Мах (char*, char*), порожденная из первого шаблона, сравнивала бы адреса строк вместо их содержимого. Поэтому нужно определить отдельную функцию Мах (char*, char*):

    char *Max(char *a, char *b) {

    return strcmp(a, b) > 0? а : b;

    }

    Когда компилятор встречает вызов какой-то функции, для его разрешения он следует такому алгоритму:

    • Сначала ищется обычная функция с соответствующими параметрами.
    • Если таковой не найдено, компилятор ищет шаблон, из которого можно было бы генерировать функцию с точным соответствием параметров.
    • Если этого сделать невозможно, компилятор вновь рассматривает обычные функции на предмет возможных преобразований типа параметров.

    Ниже приводится полный пример, иллюстрирующий различные аспекты перегрузки и специализации шаблонов.

    Листинг 10.1. Перегрузка и специализация шаблона

    //////////////////////////////////////////////

    // Functemp.cpp: Шаблоны функций.

    //

    #include <string.h>

    #include <iostream.h>

    #pragma hdrstop

    #include <condefs.h>

    // Возвращает больший из двух аргументов.

    template <class Т> Т Мах(Т а, Т b) {

    return a > b? a : b;

    }

    // Возвращает наибольший элемент массива.

    template <class Т> Т Мах(Т аrr[], size_t size) {

    Т maxVal = arr[0] ;

    for(unsigned i=l; i<size; i++)

    if (arr[i] > maxVal)

    maxVal = arr[i] ;

    return maxVal;

    }

    // Возвращает большую из двух строк.

    char *Max(char *a, char *b)

    {

    return strcmp(a, b) > 0? а : b;

    }

    // Вызывается для целочисленных аргументов // различающихся типов. long Max(long a, long b)

    {

    return Max<long>(a, b);

    }

    #pragma argsused int main(int argc, char* argv[])

    {

    int il = 11, i2 = 22;

    float fl = 1.11, f2 = 22.2;

    char str1[] = "First string.";

    char str2[] = "Second string.";

    char с = 33;

    cout << "Max int: " << Max(il, i2) << endl;

    cout<< "Max float: " “ Max(fl, f2) << endl;

    cout << "Max element: "<< Max(strl, strlen(strl)) << endl;

    cout << "Max string: " << Max(strl, str2) << endl;

    cout << "Max(int,char): " << Max(i1, c) << endl;

    return 0;

    }

    Последнее определение—Max (long, long) —требует некоторых пояснений. Эта специализированная функция вызывает явным образом функцию шаблона для сравнения двух аргументов фактического типа long. Но какой в этом смысл?

    Если не определить такую функцию, компилятор вообще не сможет вызвать, например, Мах (int, char), как в последнем операторе вывода. (Подобные сравнения являются на самом деле “признаком дурного тона”.) Имеется только шаблон, два параметра которого имеют один и тот же тип, а как говорилось выше, компилятор использует шаблон только в том случае, если можно получить точное соответствие параметров типам аргументов в вызове. Однако благодаря определению специализированной функции компилятор может разрешить вызов, преобразовав char в long.

    На рисунке показан результат работы программы.

    Рис. 10.1 Программа Functemp

    Шаблоны классов

    Шаблон класса является обобщенным определением некоторого семейства классов, имеющих схожую структуру, но различных в смысле используемых типов или констант. Синтаксис шаблона класса следующий:

    template <список параметров шаблона> class имя шаблона {тело_класса };

    В списке_параметров_шаблона .могут присутствовать элементы двух видов:

    Определение шаблона класса

    Определение шаблона класса предполагает:

    Определения функций-элементов, расположенные в теле шаблона, ничем не отличаются от определения встроенных функций-элементов обычного класса. Определения функций-элементов, располагаемые вне тела шаблона, имеют такой вид:

    template <список параметров шаблона> возвращаемый_тип имя шаблона< параметры_шаблона>::имя_функции(список_параметров) {тело_функции }

    Подобным же образом определяются статические элементы данных шаблона класса:

    template <список параметров шаблона>

    тип имя шаблона

    <параметры шаблона>::имя статического элемента[ =значение];

    Смысл всех синтаксических элементов определений будет ясен, если рассмотреть пример законченного шаблона класса:

    Листинг 10.2. Шаблон класса DataBase

    /////////////////////////////////////////

    // Deftmpl.h: Пример определения шаблона класса.

    //

    #ifndef _DEFTMPL_H

    #define _DEFTMPL_H

    template <class T, int numRec> class DataBase { protected:

    const int num.;

    bool err;

    T *base, *cp;

    public:

    DataBase (): num(numRec)

    {

    cp = base = new T[numRec];

    err = false;

    }

    ~DataBase () ( delete [] base;

    } bool Error () { return err; } T SRec(void) ;

    T &Rec(unsigned recno);

    };

    // Возвращает ссылку на текущую запись

    // и переходит к следующей.,

    template <class T, int numRec>

    Т &DataBase<T, numReO: :Rec (void)

    {

    if (cp - base == num) { err = true;

    return *(cp - 1) ;

    }

    else

    return *cp++;

    // Позиционирует указатель и возвращает ссылку

    // на текущую запись.

    template <class T, int numRec>

    T &DataBase<T, numRec>::Rec(unsigned recno)

    {

    if (recno < (unsigned)num) { err=false;

    return *(cp = base+recno);

    }

    else {

    err=true;

    return*(cp=base+num-1);

    }

    }

    #endif

    //_DEFTMPL_H

    Этот шаблон реализует примитивный “поток” или “базу данных”, являющуюся массивом записей, тип которых определяется аргументом шаблона. Класс содержит функции для обращения либо к записи с указанным индексом (Rec (unsigned)), либо к записи, на которую ссылается указатель “базы данных” (Re с (void)). В последнем случае указатель перемещается к следующей записи.

    При выходе за пределы массива устанавливается флаг ошибки, и функции Get () возвращают ссылку на последнюю запись.

    Параметрами шаблона являются формальный тип записи Т и константа — число записей в массиве.

    Функции-элементы шаблона класса, определяемые вне тела шаблона, могут объявляться как встроенные с помощь, ключевого слова inline, подобно функциям-элементам обычных классов. Например, в приведенном выше определении шаблона можно было бы написать:

    template <class T,int numRec>

    inline T &DataBase<T, numRec>::Rec(void)

    {

    if (cp-base== num) {err= true/return*(cp-1) ;

    } else

    return *cp++;

    }

    Создание представителей шаблона

    Чтобы создать из шаблона представитель конкретного класса, нужно конструировать объект, указав для его типа имя шаблона с набором конкретных аргументов (типов и констант). Каждый формальный тип в списке параметров шаблона нужно заменить на имя действительного типа. Каждая формальная константа заменяется на константу указанного в шаблоне типа:

    // Шаблон класса. template <ciass T, int О class TmplClass { ... };

    // Создание представителей шаблонных классов.

    TmplClass<long, 100> IClassObj;

    TmplClass<float, 40> *fClassPtr;

    fClassPtr = new TmplClass<float, 40>;

    После того, как представитель шаблонного класса создан, с ним можно обращаться точно так же, как с любым объектом, принадлежащим к обыч-

    ному классу. Ниже показан пример программы, использующей определение шаблона из листинга 10.2.

    Листинг 10.3. Создание и использование представителя шаблонного класса

    /////////////////////////////////////

    // Usetmpl.cpp: Использование шаблона класса. //

    #include <iostream.h>

    #pragma hdrstop

    #include <condefs.h>

    #include "Deftmpl.h"

    // Включить определение шаблона.

    // Класс записей, для которого будет создан шаблонный класс. class Record {

    char str[41] ;

    public:

    Record(void) { str[0] = 0; }

    void Set(const char *s)

    { strncpy(str, s, 40);}

    char *Get(void)

    { return str; } };

    #pragma argsused

    int main(int argc, char* argv[])

    {

    const int NumRec = 4;

    DataBase<Record, NumRec> db; // Объявление объекта

    // с 4-мя записями.

    // Инициализация массива.

    db.RecO .Set("First string.");

    db.Rec().Set("Second string.");

    db.RecO .Set("Third string.");

    db.Rec().Set("Fourth string.");

    cout.setf(ios::boolalpha);

    // Чтение с попыткой выхода за пределы массива.

    db.Rec(O); // Позиционирование на 0.

    for (int i=0; i<=NumRec; i++) {

    cout << db.RecO .Get() << " Error: ";

    cout << db.Error() << endl;

    } cout << endl;

    // Чтение с прямым указанием индекса.

    for (int i=NumRec-l; i>-l; i--) {

    cout << db.Rec(i).Get() << " Error: ";

    cout << db.Error() << endl;

    }

    return 0;

    }

    Вывод программы показан на рис. 10.2.

    В начале файла программы находится определение класса Record, который используется как аргумент шаблона DataBase. Это класс строк с конструктором по умолчанию и операциями чтения-записи содержимого строки.

    Программа создает представитель шаблонного класса DataBase< Record, 4> и выполняет над ним различные действия — запись строк в “поток”, позиционирование, чтение.

    Рис. 10.2 Программа UsetmpI

    Когда при обработке исходного файла компилятору встречается создание объекта на основе некоторого шаблона класса, он прежде всего генерирует представитель шаблона, или шаблонный класс, как мы его назвали. По существу при этом генерируется и компилируется код всех функций-элементов шаблона для данного набора его аргументов (и код некоторых вспомогательных функций). После этого компилятор может конструировать объект шаблонного класса, вызывать нужные функции-элементы объекта и т. д.

    Если создается шаблонный объект, аргументы которого совпадают с аргументами объекта, ранее созданного в текущем модуле компиляции, то новый представитель шаблона не генерируется. Данный шаблонный класс уже существует, остается только конструировать объект.

    Для удобства работы с шаблонными классами можно воспользоваться определением typedef, например:

    template<class T> TmplClass { ... };

    typedef TmplClass<int> IClass;

    IClass iCIassObj; IClass *iCiassPtr; iCIassPtr = new IClass;

    Разное

    В этом параграфе мы расскажем о некоторых возможностях шаблонов, предусмотренных в стандартном C++, но не реализуемых компилятором C++Builder. Нам кажется, что о них необходимо рассказать, хотя бы для того, чтобы, читая другие книги по C++, вы не пытались осуществить в C++Builder методики, которые на нем осуществить невозможно.

    В конце концов, C++Builder не является универсальным инструментом. Он ориентирован на визуальное программирование, а те моменты, о которых мы будем здесь говорить, второстепенны с этой, да и, пожалуй, с любой другой точки зрения.

    Если у вас есть Borland C++ 5 или более поздняя версия, и вы хотя бы немного умеете с ним работать, то можете при желании разобрать с его помощью приведенные ниже примеры.

    Специализация шаблона класса

    Подобно шаблону функции, шаблон класса может быть специализирован для специфического набора его аргументов. Для этого нужно написать явные реализации тех или иных методов шаблона для конкретных типов. Вот, например, шаблон, который генерирует класс массива объектов, в том числе символьных строк, для которых отдельно реализуется функция добавления в массив и деструктор:

    #include <iostream.h>

    #include <string.h>

    const int DefSize = 4;

    template <class T> Glass MyArray { protected:

    int size;

    int current;

    T *arr;

    public:

    MyArray (int n = DefSize) { size = n;

    current = 0;

    arr = new T[size];

    }

    ~MyArray ();

    void Insert(const T Sitem);

    T &Get(int idx) { return arr[idx]; } };

    // Общий шаблон Insert:

    template <class T> void MyArray<T>::Insert(const T Sitem)

    {

    if (current == size) return;

    arr[current++] = item;

    }

    // Специализированная Insert для параметра char*:

    void MyArray<char*>::Insert(char* const Sitem)

    {

    if (current == size) return;

    arr[current] = new char[strlen(item) + 1];

    strcpy(arr[current++], item);

    }

    // Общий деструктор:

    template <class T>

    MyArray<T>::-MyArray () ( delete[] arr; }

    // Специализированный деструктор:

    MyArray<char*>::-MyArray() (

    for (int i=0; i<size; i++)

    delete [ ] arr[i];

    delete [ ] arr;

    }

    А вот главная функция, тестирующая шаблон для “стандартного” типа int и для “специального” типа строк (т. е. char*):

    int main(void)

    {

    // Создание, заполнениеи вывод MyArray<int>.

    MyArray<int> *iArr;

    iArr = new MyArray<int>;

    int i;

    for (i=0; i<DefSize; i++) iArr->Insert (i);

    cout << "Integers: ";

    for (i=0; KDefSize; i++)

    cout << " " << iArr->Get(i);

    cout<< end1<< end1;

    delete iArr; // Уничтожение объекта.

    // Создание, заполнение и вывод MyArray<char*>.

    MyArray<char*> *sArr;

    sArr = new MyArray<char*>;

    for (i=0; KDefSize; i++) sArr->Insert("String!");

    cout << "Strings: ";

    for (i=0; KDefSize; i++)

    cout << " " << sArr->Get(i) ;

    cout << end1;

    delete sArr; // Уничтожение объекта.

    return 0;

    }

    Полная специализация шаблона

    Можно также полностью переопределить шаблон класса для какого-то конкретного типа аргумента. Это значит, что после определения общего шаблона нужно определить специализированный шаблон класса и предусмотреть переопределения всех его элементов-функций и статических элементов данных. Ниже приводится вариант предыдущего примера, использующий такую методику:

    #include <iostream.h>

    #include <string.h>

    const int DefSize = 4;

    // Общий шаблон:

    template <class T> class MyArray { protected:

    int size;

    int current;

    T *arr;

    public:

    MyArray(int n = DefSize) { size = n;

    current = 0;

    arr = new T[size];

    }

    ~MyArray() { delete[] arr; }

    void Insert(const T &item) {

    if (current == size) return;

    arr[current++] = item;

    }

    T &Get(int idx) { return arr[idx]; } } ;

    // Специализированный шаблон для char*:

    class MyArray<char*> { protected:

    int size;

    int current; char **arr;

    public:

    MyArray(int n = DefSize) { size = n;

    current = 0;

    arr = new char*[size];

    } ~MyArray() ;

    void Insert(char* const &item) { if (current == size) return;

    arr[current] = new char[strlen(item) + 1];

    strcpy(arr[current++], item);

    } char* &Get(int idx) { return arr[idx]; }

    };

    // Деструктор специализированного шаблона:

    MyArray<char*>::~MyArray() {

    for (int i=0; i<size; i++) delete [ ] arr[i] ;

    delete[] arr;

    }

    Заметьте, что все функции-элементы общего шаблона определены теперь как встроенные, а в специализированном только деструктор определен вне тела шаблона (его нельзя объявить как встроенный, поскольку он содержит оператор цикла). В общем, полное переопределение шаблона целесообразно в том случае, когда необходима специализация большинства его элементов-функций. Главная функция ничем не отличается от функции предыдущего примера.

    Функции, дружественные шаблону

    В качестве “друзей” класса чаще всего объявляют различные функции-операции, в которых участвуют объекты класса. Типичным примером может служить операция передачи объекта в поток. Для шаблона класса можно определить шаблон дружественной функции (не обязательно, конечно, операции). Такой шаблон будет порождать отдельную дружественную функцию для каждого генерируемого шаблонного класса. Вот пример шаблона дружественной функции (это модификация первого примера параграфа):

    #include <iostream.h>

    #include <string.h>

    const int DefSize = 4;

    template <class T> class MyArray { protected:

    int size;

    int current;

    T *arr;

    public: MyArray(int n = DefSize) { size = n; current = 0;

    arr = new T[size];

    } ~MyArray();

    void Insert(const T&);

    T &Get(int idx) { return arr[idx]; }

    friend ostream &operator“(ostream&, const MyArray<T>&);

    };

    // Шаблон дружественной функции-операции передачи объекта // в поток:

    template <class T>

    ostream &operator<<(ostream &os, const MyArray<T>&ma)

    {

    for (int i=0; i<ma.current; i++) os << " (" << ma.arr[i]<< "}";

    return os;

    }

    //

    // Здесь находятся общие и специализированные

    // функции-элементы... //

    Определенный таким образом шаблон функции-операции реализует передачу в поток всего объекта, в противоположность предыдущим примерам, где объекты шаблонных классов выводились поэлементно. Главная функция:

    int main(void)

    {

    MyArray<int> *iArr;

    iArr = new MyArray<int>;

    int i;

    for (i=0; KDefSize; i++) iArr->Insert (i) ;

    // Вывод объекта MyArray<int>:

    cout << "Integers: " << *iArr<< endl;

    cout << endl;

    delete iArr;

    MyArray<char*> *sArr;

    sArr = new MyArray<char*>;

    for (i=0; i<DefSize; i++) sArr->Insert("String!");

    // Вывод объекта MyArray<char*>:

    cout << "Strings: "<< *sArr << endl;

    delete sArr;

    return 0;

    }

    Результат работы программы показан на рис. 10.3.

    Рис. 10.3 Пример с шаблоном

    дружественной

    функции-операции

    Порождение представителей шаблона

    Этот раздел посвящен тому, каким образом C++Builder генерирует шаблонные классы и функции в программах, состоящих из нескольких модулей исходного кода. Несколько модулей проекта могут подключать один и тот же заголовочный файл с шаблоном, и создавать объекты одного и того же шаблонного класса (с одинаковым набором аргументов шаблона). В C++Builder имеются средства, позволяющие избежать дублирования кода в такой ситуации.

    Установки проекта и ключи компилятора

    В диалоге Project Options на странице C++ имеется флажок Templates: External (рис. 10.4). По умолчанию он сброшен, что означает оптимальное, или “интеллигентное”, порождение представителей шаблонов классов и функций.

    Рис. 10.4 Страница C++ диалога Project Options

    При сброшенном флажке External компилятор порождает глобальные представители шаблонов для всех модулей, где создаются объекты шаблонных классов (или вызываются шаблонные функции). Это возможно только в том случае, если компилятор при обработке модуля “видит” все определение шаблона, со всеми его функциями-элементами. Однако объектный код не обязательно генерируется для всех шаблонных методов (определенных как не-встроенные). По умолчанию код генерируется для методов:

    Что означает последний пункт, будет рассказано чуть ниже.

    После этого компоновщик ilink32.exe анализирует код объектных файлов и помещает в исполняемый файл только один экземпляр функции для каждой комбинации шаблон/аргументы.

    Если же флажок External будет установлен, то компилятор вообще не будет генерировать никакого кода для не-встроенных функций класса, а будет рассматривать их вызовы как внешние ссылки. Такой вариант может иметь смысл, если, допустим, вы используете в своем проекте библиотеку, в заголовочных файлах которой определяются некоторые шаблоны и в которой уже имеется компилированный код для всех имеющих смысл представителей шаблонов.

    Ключи компилятора

    Сброшенному флажку External в диалоге Project Options соответствует ключ командной строки компилятора -Jgd, а установленному — -Jgx. Посредством директивы препроцессора #pragma option можно указать ту или иную опцию для конкретного файла. Не следует забывать о том, что компиляция и компоновка программы — два различных этапа ее создания. Компоновщик не знает, что вы задали в диалоге Project Options для компилятора. В любом случае компоновщик не будет включать в исполняемый код повторные определения функций шаблона.

    Явное создание представителя шаблона

    В подавляющем большинстве случаев прикладному программисту нужно только сбросить флажок External в диалоге Project Options и больше не беспокоиться о том, как написанные им шаблоны классов будут обрабатываться. Однако, если вы хотите создать, например, динамическую библиотеку на основе шаблона, которая будет содержать код всех его функций-элементов, то для генерирования полного представителя шаблона вам придется воспользоваться директивой template.

    Следующий пример включает в себя два исходных файла и один заголовочный, в котором определяется простой шаблон. Главный исходный модуль программы создает два шаблонных объекта для его аргументов int и float, но, поскольку модуль компилируется с директивой #pragma option -Jgx, то никакого кода для представителей шаблона в нем не создается. Вместо этого во втором исходном модуле (компилируемом с ключом -Jgx) явным образом генерируется полный представитель шаблона для аргумента float, а также неявно генерируется представитель для int, так как модуль ссылается на него.

    Листинг 10.4. Директива порождения представителя шаблона

    /////////////////////////////////////////

    // Simptmpl.h: Простой шаблон класса.

    //

    template <class T> class Simple {

    protected:

    int size;

    int current;

    T *arr;

    public:

    Simple(int) ;

    ~Simple () ;

    void Insert(T item)

    { if (current != size) arr[current++] = item; }

    T SGet(int) ;

    };

    template <class T> inline Simple<T>::Simple(int n): size(n),

    current (0), arr(new T[n]) {}

    template <class T> Simple<T>::~Simple() { delete[] arr; }

    template <class T> T SSimple<T>::Get(int idx) { return arr[idx]; }

    void Somefunc(int);

    ////////////////////////////////////////

    // Instance.cpp: Порождение представителей шаблона. //

    #pragma option -Jgx

    #include <iostream.h>

    #pragma hdrstop

    #include <condefs.h>

    #include "Simptmpl.h"

    USEUNIT("Somefunc.cpp") ;

    #pragma argsused

    int main(int argc, char* argv[])

    {

    const int Num = 9;

    Simple<int> ia(Num);

    Simple<float> fa(Num);

    fa.Insert(3.14) ;

    cout << "Float In main(): " << fa.Get(O) << endl;

    for (int i=0; i<Num; i++)

    ia.Insert(i * 2);

    cout << "From main(): ";

    for (int i=0; i<Num; i++)

    cout << ia.Get(i) << " ";

    cout << end1;

    Somefunc(10);

    cin.ignore();

    return 0;

    }

    //////////////////////////////////////////

    // Somefunc.cpp: Функция, использующая шаблон класса. //

    #pragma option -Jgd #include <iostream.h>

    #pragma hdrstop #include "Simptmpl.h"

    //

    // Следующая строка генерирует явный представитель шаблона

    // для типа float, хотя в данном модуле он не используется:

    // template class Simple<float>;

    void Somefunc(int n)

    {

    Simple<int> iArr(n);

    for (int i=0; i<n; i++) iArr.Insert (i);

    cout << "From Somefunc(): "<< n;

    for (int i=0; i<n; i++)

    cout<<" "<< iArr.Get(i);

    cout <<end1; ;

    }

    Вывод программы показан на рис. 10.5.

    Рис. 10.5 Результат работы программы Instance

    Ключевое слово typename

    Это ключевое слово может применяться в двух случаях. Во-первых, им можно заменять ключевое слово class в списке параметров шаблона. Такое дополнение сделано в ANSI C++ потому, что ключевое слово class в списке параметров не вполне отражает положение дел;

    параметром шаблона может быть любой тип, а не только класс, и стандартный синтаксис может вводить некоторых в заблуждение. Следующие две нотации эквивалентны:

    template <class T> class SomeClass {...};

    template <typename T> class SomeClass {...};

    Во-вторых, typename необходимо, если шаблон определяет некоторые объекты еще не объявленных типов. Рассмотрите такой шаблон функции:

    template <class T>

    void Func(Т Sargi, typename T::Inner arg2)

    {

    typename T::Inner tiObj;

    // Локальный объект

    // типа Т::Inner.

    // ...

    Нам еще как-то не приходилось говорить, что объявление класса может содержать вложенные объявления других типов, в том числе классов. Например:

    class One { public:

    class Two {

    // Элементы класса Two...

    } ;

    private:

    Two objOfTwo;

    // Другие элементы One... }

    Сослаться на вложенный тип можно либо через существующий объект, либо с помощью операции разрешения области действия с префиксом имени класса. Очень часто так объявляют перечисления и константы, пример чему вы могли видеть в классе ios, объявляющем перечислимые типы и их константы вида ios::fixed и т. п.

    Предполагается, что любые классы, для которых будет вызываться шаблонная функция, должны объявлять тип с именем Inner. Но заранее неизвестно, что это за тип, и в этом случае объекты или аргументы функции, принадлежащие к этому типу, следует объявлять с ключевым словом

    typename.

    Заключение

    В этой главе вы увидели, насколько широкие и мощные возможности предоставляют программисту шаблоны функций и классов. Можно легко объявлять нужные вам классы, создавая представители уже имеющихся шаблонов; существует много больших библиотек, реализующих этот принцип. К их числу относятся, например, библиотека контейнерных классов Борланда и Стандартная библиотека шаблонов ANSI C++ (STL), о которой мы немного расскажем в следующей главе.